package com.dragon.modules.yuegou.model.rbac;

import com.dragon.modules.yuegou.enums.IMenu;
import com.dragon.modules.yuegou.web.Tree;
import com.dragon.modules.yuegou.web.TreeIterator;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;

import javax.annotation.Nullable;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * 资源节点，资源节点与资源节点之间组成了一个树形结构
 * 
 * @author liuwu
 *
 */
@ApiModel(description = "资源节点(可以被权限系统控制的资源：菜单、接口)")
public class RbacResourceNode extends RbacResource {

	private static final String[] SELECT_FLAGS = { "  ", "√", "⚪", "×" };

	public static final int UNSELECTED = 0;
	public static final int SELECTED = 1;
	public static final int HALFSELECTED = 2;
	public static final int FORBIDDEN = 3;

	/**
	 * 是否选择：0表示未选择，1表示全选，2表示半选（其子节点被选择了，但是本身没有被选择）<br>
	 * <ol>
	 * <li>未选择表示无权限</li>
	 * <li>全选表示有该资源节点及其所有子节点的权限</li>
	 * <li>半选表示有该节点的子节点的部分权限</li>
	 * <li>禁用表示所属的权限组没有开发对应资源的权限</li>
	 * </ol>
	 */
	@ApiModelProperty("是否选择：0表示未选择，1表示全选，2表示半选（其子节点被选择了，但是本身没有被选择），3表示禁用")
	private int hasSelected;

	/**
	 * 本资源节点的子资源列表
	 */
	@Nullable
	@ApiModelProperty("子节点列表")
	private List<RbacResourceNode> childs;

	public RbacResourceNode(RbacResource resource) {
		super(resource);
	}

	public RbacResourceNode(IMenu menu) {
		super(String.valueOf(menu.getId()), String.valueOf(menu.getParentId()), menu.getAlias(), menu.getPath(), null,null);
	}

	@JsonCreator
	public RbacResourceNode(@JsonProperty("id") String id, @JsonProperty("parentId") String parentId,
			@JsonProperty("name") String name, @JsonProperty("path") String path,
			@JsonProperty("roles") Set<String> roles,@JsonProperty("sortNo") Integer sortNo) {
		super(id, parentId, name, path, roles,sortNo);
	}

	/**
	 * 获取选择状态：0表示未选择，1表示全选，2表示半选（其子节点被选择了，但是本身没有被选择）
	 * 
	 * @return hasSelected
	 * @see #UNSELECTED
	 * @see #SELECTED
	 * @see #HALFSELECTED
	 */
	public int getHasSelected() {
		return hasSelected;
	}


	/**
	 * 设置选择状态：0表示未选择，1表示全选，2表示半选（其子节点被选择了，但是本身没有被选择）
	 * 
	 * @param hasSelected 选择状态
	 * @see #UNSELECTED
	 * @see #SELECTED
	 * @see #HALFSELECTED
	 */
	public void setHasSelected(int hasSelected) {
		this.hasSelected = hasSelected;
	}
	/**
	 * 判断资源是否被选中
	 * 
	 * @return true表示选中，false表示未被选中
	 */
	@JsonIgnore
	public boolean isSelect() {
		return hasSelected == SELECTED;
	}

	/**
	 * 获取子资源列表
	 * 
	 * @return 子资源列表
	 */
	public List<RbacResourceNode> getChilds() {
		return childs;
	}

	/**
	 * 设置子资源列表
	 * 
	 * @param childs 子资源列表
	 */
	public void setChilds(List<RbacResourceNode> childs) {
		this.childs = childs;
	}

	/**
	 * 移除被禁用的子节点
	 * 
	 * @return this
	 */
	public RbacResourceNode removeForbiddenChilds() {
		LinkedList<RbacResourceNode> queue = new LinkedList<>();
		queue.add(this);
		while (!queue.isEmpty()) {
			RbacResourceNode node = queue.remove();
			if (node.childs != null && node.childs.isEmpty()) {
				node.childs = node.childs.stream().filter(child -> child.hasSelected != FORBIDDEN)
						.collect(Collectors.toCollection(LinkedList::new));
				queue.addAll(node.childs);
			}
		}
		return this;
	}

	/**
	 * 构造资源树
	 * 
	 * @param resources          资源集合
	 * @param selectPredicate    选择判断函数
	 * @param forbiddenPredicate 禁用判断函数
	 * @return 资源树
	 */
	public static Tree<RbacResourceNode> buildSelectedTree(Collection<RbacResource> resources,
														   Predicate<RbacResource> selectPredicate, Predicate<RbacResource> forbiddenPredicate) {
		if (resources.isEmpty()) {
			throw new IllegalArgumentException("资源树不能为空");
		}
		List<RbacResourceNode> nodes = resources.stream().map(RbacResourceNode::new).collect(Collectors.toList());
		Tree<RbacResourceNode> tree = buildTree(nodes);
		TreeIterator<RbacResourceNode> itr = tree.treeIterator(Tree.IteratorStrategy.BREADTH_FIRST);
		while (itr.hasNext()) {
			RbacResourceNode node = itr.next();
			List<RbacResourceNode> childs = itr.childs();
			if (!childs.isEmpty()) {
				node.setChilds(childs);
			}
			if (forbiddenPredicate.test(node)) {
				node.setHasSelected(FORBIDDEN);
			} else if (selectPredicate.test(node)) {
				node.setHasSelected(SELECTED);
				for (RbacResourceNode p : itr.path()) {
					if (p.getHasSelected() != SELECTED) {
						p.setHasSelected(HALFSELECTED);
					}
				}
			}
		}
		return tree;
	}

	@Override
	public String toString() {
		return String.format("[%s]%s(%s)", SELECT_FLAGS[hasSelected], name, path);
	}
}
